﻿[<AutoOpen>]
module FODATAC.CommonTypes

open System.Reflection
open System.Threading.Tasks
open System
open System.Runtime.Serialization
open Microsoft.Data.Edm
open System.Collections.Generic

/// <summary>
/// Setter info
/// </summary>
type Setter = 
    | Field of FieldInfo
    | Property of PropertyInfo

// converts F# Async to Task in proper manner
let convertToTask<'T> asyn = 
    let tcs1 = new TaskCompletionSource<'T>()
    let t1 = tcs1.Task
    Async.StartWithContinuations(asyn, (fun k -> tcs1.SetResult(k)), (fun exn -> tcs1.SetException(exn)), fun exn -> ())
    t1

type MetaData(model : IEdmModel) = 
    let collectionNames = Dictionary<Type, String>()
    static member Pluralize(typeName : String) = typeName + "s"
    
    static member GetTypeNameAndNameSpace(itemType : Type) : string * string = 
        try 
            
            let someAttr = itemType.GetTypeInfo().GetCustomAttributes(typeof<DataContractAttribute>, false) |> (Seq.tryPick Some)
            match someAttr with
            | Some(:? DataContractAttribute as attr) -> 
                let targetName = 
                    if attr.Name <> null then attr.Name
                    else itemType.Name
                
                let targetNameSpace = 
                    if attr.Namespace <> null then attr.Namespace
                    else itemType.Namespace
                
                (targetName, targetNameSpace)
            | _ -> itemType.Name, itemType.Namespace
        with _ -> failwith "Can't find a member for type %s" itemType.FullName
    
    static member GetQualifiedName(itemType : Type) : string = 
        let targetName, targetNameSpace = MetaData.GetTypeNameAndNameSpace itemType
        targetNameSpace + "." + targetName
    
    member this.GetTypeKind(itemType : Type) = 
        let targetName, targetNameSpace = MetaData.GetTypeNameAndNameSpace itemType
        let elem = ((model.SchemaElements) |> Seq.tryFind (fun x -> x.Name = targetName && x.Namespace = targetNameSpace))
        match elem with
        | None -> EdmTypeKind.Primitive
        | Some(:? IEdmType as elem) -> elem.TypeKind
        | _ -> failwith "unsupported schema element %A" elem
    
    member this.GetEntitySet(entityType : System.Type) = 
        let name, nameSpace = MetaData.GetTypeNameAndNameSpace entityType
        (MetaData.Pluralize name) |> model.FindDeclaredEntityContainer(nameSpace + "." + name).FindEntitySet
    
    member this.GetSchemaElementForEntity(entityType : System.Type) = 
        MetaData.GetQualifiedName entityType |> model.FindDeclaredType :?> IEdmEntityType
    member this.GetSchemaElementForComplexType(complexType : System.Type) = 
        MetaData.GetQualifiedName complexType |> model.FindDeclaredType :?> IEdmComplexType
    member this.SetCollectionName(itemType, collectionName) = collectionNames.Add(itemType, collectionName)
    
    member this.GetCollectionName(itemType : Type) = 
        let bool, name = collectionNames.TryGetValue itemType
        if bool then name
        else 
            let name, _ = MetaData.GetTypeNameAndNameSpace itemType
            name |> MetaData.Pluralize
    
    member this.GetKeys(entityType : System.Type) = 
        let rec getKey entityType = 
            let schemaElement = this.GetSchemaElementForEntity entityType
            match schemaElement.DeclaredKey, entityType.GetTypeInfo().BaseType with
            | null, baseType when baseType <> typeof<obj> -> getKey baseType
            | other, _ -> other
        (getKey entityType)
        |> Seq.map (fun k -> k.Name, k.Type)
        |> Seq.toList
    
    member this.Model = model
